﻿
using System.Text;

namespace CodeToHtml {
    public class CodeReader : BetterStringReader {
        private CodeReaderParameters Parameters { get; set; }

        public CodeReader(string code, CodeReaderParameters parameters)
            : base(CleanCode(code)) {
            Parameters = parameters;
        }

        private static string CleanCode(string code) {
            code = code.Replace("\r", "");
            return code;
        }

        public CodeItem ReadCodeItem() {
            CodeItem result;
            if (TryReadCodeString(out result)) {
                return result;
            }
            if (TryReadLineComment(out result)) {
                return result;
            }
            if (TryReadBlockComment(out result)) {
                return result;
            }
            if (TryReadKeyword(out result)) {
                return result;
            }
            if (TryReadDelimiter(out result)) {
                return result;
            }
            if (TryReadOther(out result)) {
                return result;
            }
            return result;
        }

        private bool TryReadDelimiter(out CodeItem codeItem) {
            int ch = Peek();
            if (IsDelimiter((char)ch)) {
                string str = Read(1);
                codeItem = new CodeItem(CodeItemType.Delimiter, str);
                return true;
            }
            codeItem = null;
            return false;
        }

        private bool TryReadOther(out CodeItem codeItem) {
            StringBuilder sb = new StringBuilder();
            while (!IsEndOfString()) {
                int ch = Peek();
                if (IsDelimiter((char)ch)) {
                    break;
                }
                sb.Append((char)Read());
            }
            codeItem = new CodeItem(CodeItemType.Other, sb.ToString());
            return true;
        }

        private bool TryReadKeyword(out CodeItem codeItem) {
            codeItem = null;
            foreach (string keyword in Parameters.Keywords) {
                if (StartsWith(keyword)) {
                    string str;
                    if (LeftToRead == keyword.Length) {
                        str = Read(keyword.Length);
                        codeItem = new CodeItem(CodeItemType.Keyword, str);
                        return true;
                    }
                    str = Peek(keyword.Length + 1);
                    char ch = str[keyword.Length];
                    if (IsDelimiter(ch)) {
                        str = Read(keyword.Length);
                        codeItem = new CodeItem(CodeItemType.Keyword, str);
                        return true;
                    }
                }
            }
            return false;
        }

        private bool TryReadLineComment(out CodeItem codeItem) {
            codeItem = null;
            foreach (string lineComment in Parameters.LineComments) {
                string str;
                if (TryReadStartToEnd(out str, lineComment, "\n", false)) {
                    codeItem = new CodeItem(CodeItemType.Comment, str);
                    return true;
                }
            }
            return false;
        }
        private bool TryReadBlockComment(out CodeItem codeItem) {
            codeItem = null;
            for (int i = 0; i < Parameters.BlockCommentStarts.Length; i++) {
                string str;
                if (TryReadStartToEnd(out str, Parameters.BlockCommentStarts[i], Parameters.BlockCommentStops[i], false)) {
                    codeItem = new CodeItem(CodeItemType.Comment, str);
                    return true;
                }
            }
            return false;
        }

        private bool TryReadCodeString(out CodeItem codeItem) {
            codeItem = null;
            foreach (string stringCharacter in Parameters.StringCharacters) {
                string str;
                if (TryReadStartToEnd(out str, stringCharacter, stringCharacter, true)) {
                    codeItem = new CodeItem(CodeItemType.String, str);
                    return true;
                }
            }
            return false;
        }

        private bool TryReadStartToEnd(out string str, string start, string end, bool escapesAllowed) {
            str = null;
            if (StartsWith(start)) {
                StringBuilder sb = new StringBuilder();
                sb.Append(Read(start.Length));
                while (!IsEndOfString()) {
                    if (escapesAllowed && IsEscape()) {
                        sb.Append((char)Read()); // read the escape
                        sb.Append((char)Read()); // read the character being escaped
                    } else if (StartsWith(end)) {
                        sb.Append(Read(end.Length));
                        str = sb.ToString();
                        return true;
                    } else {
                        sb.Append((char)Read());
                    }
                }
            }
            return false;
        }

        private bool IsEscape() {
            foreach (char escapeCharacter in Parameters.EscapeCharacters) {
                if (StartsWith(escapeCharacter)) {
                    return true;
                }
            }
            return false;
        }

        private bool IsDelimiter(char ch) {
            foreach (char delimiter in Parameters.Delimiters) {
                if (ch == delimiter) {
                    return true;
                }
            }
            return false;
        }
    }
}
